#!/usr/bin/env node

var fs = require('fs');
var verifyRequirements = require('./utils/verify-requirements');
var safeExec = require('./utils/child-process-wrapper.js').safeExec;
var path = require('path');

// Executes an array of commands in series
function executeCommands(commands, done, index) {
  index = (index == undefined ? 0 : index);
  if (index < commands.length) {
    var command = commands[index];
    if (command.message)
      console.log(command.message);
    var options = null;
    if (typeof command !== 'string') {
      options = command.options;
      command = command.command;
    }
    safeExec(command, options, executeCommands.bind(this, commands, done, index + 1));
  }
  else
    done(null);
}

function printArgs(args) {
  out = "";
  for(key in args) {
    out += "--"+key+"="+args[key]+" ";
  }
  return out;
}

function makeSqlite3Command() {
  var nodeGypPath = '"' + path.resolve(__dirname, '..', 'build', 'node_modules', 'npm', 'node_modules', '.bin', 'node-gyp') + '"';
  var appPackageJSON = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'package.json')));
  var targetVersion = appPackageJSON['electronVersion'];
  var targetPlatform = require('os').platform();
  if(targetPlatform == "win32") {
    // As of Electron 0.29.2, all windows machines, even if they're 64 bit,
    // return "ia32" as the arch
    var targetArch = "ia32"
  } else {
    var targetArch = require('os').arch();
  }

  // Use our local version of npm (npm 3x) to build sqlite
  var npmPath = '"' + path.resolve(__dirname, '..', 'build', 'node_modules', '.bin', 'npm') + '"';
  return npmPath + " install https://github.com/bengotow/node-sqlite3/archive/master.tar.gz --ignore-scripts && cd node_modules/sqlite3 && "+nodeGypPath+" configure rebuild --target="+targetVersion+" --arch="+targetArch+" --target_platform="+targetPlatform+" --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/node-v44-"+targetPlatform+"-"+targetArch
}

function bootstrap() {
  var apmInstallPath = path.resolve(__dirname, '..', 'apm');
  if (!fs.existsSync(apmInstallPath))
    fs.mkdirSync(apmInstallPath);
  if (!fs.existsSync(path.join(apmInstallPath, 'node_modules')))
    fs.mkdirSync(path.join(apmInstallPath, 'node_modules'));

  var apmPath = '"' + path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager', 'bin', 'apm') + '"';
  var apmFlags = process.env.JANKY_SHA1 || process.argv.indexOf('--no-color') !== -1 ? ' --no-color' : '';

  var npmFlags = ' --userconfig=' + '"' + path.resolve('.npmrc') + '" ';

  var gruntPath = '"' + path.resolve(__dirname, '..', 'build', 'node_modules', '.bin', 'grunt') + '"';

  var packagesToDedupe = ['fs-plus', 'humanize-plus', 'roaster', 'season', 'grim'];

  // use the system version of npm to install build folder dependencies, including our
  // own copy of NPM 3.x, which we'll use for subsequent commands
  var buildInstallCommand = 'npm' + npmFlags + 'install';
  var buildInstallOptions = {cwd: path.resolve(__dirname, '..', 'build')};
  var npmPath = '"' + path.resolve(__dirname, '..', 'build', 'node_modules', '.bin', 'npm') + '"';

  // Use our local version of npm in ./build to install apm. This ensures it gets a
  // flat dependency tree.
  var apmInstallCommand = npmPath + npmFlags + '--target=0.10.40 ' + 'install';
  var apmInstallOptions = {cwd: apmInstallPath};

  var apmDedupeCommand = npmPath + npmFlags + '--target=0.10.40 ' + 'dedupe';
  var apmDedupeOptions = {cwd: path.join(apmInstallPath, 'node_modules', 'npm')};

  var rebuildSqlite3Command = makeSqlite3Command();

  var moduleInstallCommand = apmPath + ' install' + apmFlags;
  var dedupeApmCommand = apmPath + ' dedupe' + apmFlags;
  var semverOptions = {cwd: path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager')};

  if (process.argv.indexOf('--no-quiet') === -1) {
    buildInstallCommand  += ' --loglevel error';
    apmInstallCommand    += ' --loglevel error';
    moduleInstallCommand += ' --loglevel error';
    dedupeApmCommand     += ' --quiet';
    buildInstallOptions.ignoreStdout = true;
    apmInstallOptions.ignoreStdout = true;
  }

  // apm ships with 32-bit node so make sure its native modules are compiled
  // for a 32-bit target architecture
  if (process.env.JANKY_SHA1 && process.platform === 'win32')
    apmInstallCommand += ' --arch=ia32';

  m1  = "\n---> Installing N1 build tools\n"
  m1 += "     This goes inside the `build` folder and runs `npm install`\n"
  m1 += "     It will use the system `npm` to bootstrap our own N1 npm.\n"
  m1 += "     Our build tools (like Grunt) need to be compiled against Node via `npm`.\n"
  m1 += "     Everything else needs to be compiled against Chromium with `apm`.\n\n"
  m1 += "     $ "+buildInstallCommand+" "+printArgs(buildInstallOptions)+"\n"

  m2  = "\n\n---> Installing apm\n"
  m2 += "     This installs apm via N1's `npm`\n"
  m2 += "     We use this local apm copy to install all N1 dependencies & packages\n\n"
  m2 += "     $ "+apmInstallCommand+" "+printArgs(apmInstallOptions)+"\n"

  m2a  = "\n\n---> Flattening apm package tree\n"
  m2a += "     This runs `npm dedupe` on apm's `npm` dependency.\n"
  m2a += "     We use this to prevent paths over 260 characters on Windows.\n\n"
  m2a += "     $ "+apmDedupeCommand+" "+printArgs(apmDedupeOptions)+"\n"

  m3  = "\n\n---> Cleaning apm via `apm clean`\n"

  m4  = "\n\n---> Installing N1 dependencies & packages via `apm install`\n\n"
  m4 += "     $ "+moduleInstallCommand+"\n"

  m5  = "\n\n---> De-duping packages `apm clean`\n\n"
  m5 += "     $ apm "+packagesToDedupe.join(' ')+"\n"

  m6  = "\n\n---> Request version `apm clean`\n\n"
  m6 += "     $ apm request semver "+printArgs(semverOptions)+"\n"

  m7  = "\n\n---> Getting latest Electron\n\n"
  var gruntCmd = ""
  var downloadElectronCmd = gruntPath + " download-electron --gruntfile build/Gruntfile.coffee"
  m7 += "     $ "+downloadElectronCmd

  var commands = [
    {
      command: buildInstallCommand,
      message: m1,
      options: buildInstallOptions
    },
    {
      command: apmInstallCommand,
      message: m2,
      options: apmInstallOptions
    },
    {
      command: apmDedupeCommand,
      message: m2a,
      options: apmDedupeOptions
    },
    {
      command: apmPath + ' clean' + apmFlags,
      message: m3
    }
  ];

  // we need this because we don't put our modules in node_modules and npm
  // install doesn't find them. Run APM install on each package directory manually.
  internalPackagesDir = path.resolve(__dirname, '..', 'internal_packages');
  internalPackages = fs.readdirSync('internal_packages');
  internalPackages.forEach(function(dir) {
    var dirPackageJSONPath = path.join(internalPackagesDir, dir, 'package.json');
    // On windows and linux, invoking the apm command is very slow even when there are no
    // dependencies. Make it faster by not calling unless we find there are deps.
    if (fs.existsSync(dirPackageJSONPath)) {
      var dirPackageJSON = JSON.parse(fs.readFileSync(dirPackageJSONPath));
      if (dirPackageJSON.dependencies && (Object.keys(dirPackageJSON.dependencies).length > 0)) {
        commands.push({
          command: moduleInstallCommand,
          message: "Installing dependencies for "+dir,
          options: {cwd: path.join(internalPackagesDir, dir) }
        });
      }
    }
  });

  commands = commands.concat([
    {
      command: moduleInstallCommand,
      message: m4
    },
    {
      command: dedupeApmCommand + ' ' + packagesToDedupe.join(' '),
      message: m5
    },
    {
      command: dedupeApmCommand + ' request semver',
      message: m6,
      options: semverOptions
    },
    {
      command: downloadElectronCmd,
      message: m7
    },
    {
      command: rebuildSqlite3Command,
      message: "Building sqlite3 with command: "+rebuildSqlite3Command
    }
  ]);

  process.chdir(path.dirname(__dirname));
  executeCommands(commands, function() {
    console.log("---------------------------------------------");
    console.log("script/bootstrap completed successfully. You can start\nN1 via ./N1.sh --dev, or run tests with ./N1.sh --test");
    process.exit();
  });
}

verifyRequirements(function(error, successMessage) {
  if (error) {
    console.log(error);
    process.exit(1);
  }

  console.log(successMessage);
  bootstrap();
});
